package org.lds.scheduled.job.client.starter;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.lds.scheduled.job.admin.client.AdminClient;
import org.lds.scheduled.job.admin.client.core.model.request.RegisterJobRequest;
import org.lds.scheduled.job.admin.client.core.model.response.RegisterJobResponse;
import org.lds.scheduled.job.client.IScheduledJob;
import org.lds.scheduled.job.client.ScheduledJob;
import org.lds.scheduled.job.client.enums.ClientErrorEnum;
import org.lds.scheduled.job.client.model.MethodCache;
import org.lds.scheduled.job.client.service.AbstractScheduledJobService;
import org.lds.scheduled.job.common.exception.BusinessException;
import org.lds.scheduled.job.common.model.ScheduledJobModel;
import org.lds.scheduled.job.common.model.dto.ResultDTO;
import org.lds.scheduled.job.common.util.*;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import java.lang.reflect.Method;
import java.util.*;

@Slf4j
@RequiredArgsConstructor
public class RegisterJobStarter implements ApplicationRunner, ApplicationContextAware {
    private final AdminClient adminClient;
    private final AbstractScheduledJobService scheduledJobService;

    private ApplicationContext applicationContext;

    @Value("${server.port}")
    private int port;

    @Value("${spring.application.name}")
    private String appName;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        List<ScheduledJobModel> scheduledJobModels = jobs();
        if (CollectionUtils.isEmpty(scheduledJobModels)) return;

        ScheduledUtil.defaultClosableScheduled(() -> execute(scheduledJobModels));
    }

    private List<ScheduledJobModel> jobs() {
        Map<String, IScheduledJob> scheduledJobMap = applicationContext.getBeansOfType(IScheduledJob.class);
        if (MapUtils.isEmpty(scheduledJobMap)) return null;

        Set<ScheduledJobModel> scheduledJobModels = new HashSet<>();
        Map<String, MethodCache> methodCacheMap = new HashMap<>();
        scheduledJobMap.forEach((beanName, scheduledJob) -> {
            Method[] methods = scheduledJob.getClass().getDeclaredMethods();
            for (Method method : methods) {
                ScheduledJob annotation = method.getAnnotation(ScheduledJob.class);
                if (Objects.nonNull(annotation)) {
                    String key = annotation.key();
                    AssertUtil.assertTrue(StringUtils.isNotEmpty(key), ClientErrorEnum.JOB_KEY_NOT_NULL);

                    ScheduledJobModel scheduledJobModel = new ScheduledJobModel(key, annotation.triggerRuleType(), annotation.triggerRuleContent());
                    AssertUtil.assertTrue(!scheduledJobModels.contains(scheduledJobModel), ClientErrorEnum.JOB_KEY_REPEAT);

                    scheduledJobModels.add(scheduledJobModel);
                    methodCacheMap.put(ScheduledJobUtil.jobKey(appName, key), new MethodCache(method, scheduledJob));
                }
            }
        });

        scheduledJobService.initCache(methodCacheMap);
        return new ArrayList<>(scheduledJobModels);
    }

    private void execute(List<ScheduledJobModel> scheduledJobModels) {
        try {
            ResultDTO<RegisterJobResponse> resultDTO = adminClient.registerJob(RegisterJobRequest.builder()
                    .appName(adminClient.getAdminClientProperties().getAppName())
                    .appInstanceIp(IPUtil.getLocalIp())
                    .appInstancePort(port)
                    .scheduledJobModels(scheduledJobModels)
                    .build());
            log.info("注册job成功 result={}", JsonUtil.toJson(resultDTO));
        } catch (Exception e) {
            log.error("注册job失败 cause:{}", e.getMessage());
            throw new BusinessException(ClientErrorEnum.REGISTER_APP_INSTANCE_FAIL);
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
